package com.fitso.model.dao;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

import java.util.List;

import org.apache.commons.lang.math.RandomUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.junit.Before;
import org.junit.Test;

import com.fitso.model.bean.User;
import com.fitso.model.bean.journal.FoodJournal;
import com.fitso.model.bean.journal.FoodJournalEntry;
import com.fitso.model.bean.nutrition.Food;

/**
 * <p>
 * Abstract test that verifies behavior of any implementation of
 * {@link FoodJournalDao}
 * </p>
 * 
 * @author timothystorm
 */
public abstract class FoodJournalDaoDB_IT extends DatabaseIntegrationTestUnit {
	FoodJournalDao journalDao;
	UserDao userDao;
	List<User> users;
	FoodDao foodDao;
	List<Food> foods;

	@Before
	public void setUp() throws Exception {
		super.setUp();

		userDao = newUserDao();
		users = userDao.read();

		foodDao = newFoodDao();
		foods = foodDao.read();

		journalDao = newFoodJournalDao();
	}

	public abstract UserDao newUserDao();

	public abstract FoodDao newFoodDao();

	public abstract FoodJournalDao newFoodJournalDao();

	@Test
	public void test_round_tripping() {
		User user = randomUser();

		FoodJournal journal = new FoodJournal();
		journal.addEntry(user, new DateTime(), randomFood());
		journal.addEntry(user, new DateTime(), randomFood());

		// save a new journal
		journalDao.createOrUpdate(journal);

		// verify what went in comes out
		FoodJournal fetchedJournal = journalDao.read(user, new DateTime());
		assertEquals(journal, fetchedJournal);

		// verify updates don't screw-up
		journal.addEntry(user, new DateTime(), randomFood());
		journalDao.createOrUpdate(journal);
		fetchedJournal = journalDao.read(user, new DateTime());
		assertEquals(journal, fetchedJournal);

		// verify modifications don't screw-up
		journal.getEntries().get(0).setAt(new DateTime().minusDays(1));
		journalDao.createOrUpdate(journal);
		fetchedJournal = journalDao.read(user, new DateTime());
		assertEquals(fetchedJournal.getEntries().size(), journal.getEntries().size() - 1);

		// delete the journal
		journalDao.delete(journal);
		fetchedJournal = journalDao.read(user, new DateTime());
		assertNull(fetchedJournal);
	}

	@Test
	public void test_saveOrUpdate_empty_journal() {
		User user = randomUser();

		// save an empty journal
		FoodJournal journal = new FoodJournal();
		journalDao.createOrUpdate(journal);

		// fetch the journal
		FoodJournal fetchedJournal = journalDao.read(user, new DateTime());
		assertNull(fetchedJournal);
	}

	@Test
	public void test_saveOrUpdate() {
		User user = randomUser();

		// add some entries and save
		FoodJournal journal = new FoodJournal();
		journal.addEntry(user, new DateTime(), randomFood());
		journal.addEntry(user, new DateTime(), randomFood());
		journalDao.createOrUpdate(journal);

		// re-fetch the journal
		FoodJournal fetchedJournal = journalDao.read(user, new DateTime());
		assertEquals(journal, fetchedJournal);

		// add a new journal entry
		FoodJournalEntry entry = new FoodJournalEntry(user, new DateTime(), randomFood());
		journal.addEntry(entry);
		journalDao.createOrUpdate(journal);

		// re-fetch the journal
		fetchedJournal = journalDao.read(user, new DateTime());
		assertEquals(journal, fetchedJournal);

		// modify the entry
		entry.setAt(new DateTime().minusMinutes(10));
		journalDao.createOrUpdate(journal);

		// re-fetch the journal
		fetchedJournal = journalDao.read(user, new DateTime());
		assertEquals(journal, fetchedJournal);

		// remove a journal entry
		journal.removeEntry(entry);
		journalDao.createOrUpdate(journal);

		// re-fetch the journal
		fetchedJournal = journalDao.read(user, new DateTime());
		assertEquals(journal, fetchedJournal);
	}

	@Test
	public void test_fetch() {
		User user = randomUser();
		DateTime validDate = new DateTime().withDate(2010, DateTimeConstants.MARCH, 2);
		DateTime invalidDate = new DateTime().withDate(2009, DateTimeConstants.JANUARY, 1);

		// fetch nothing
		FoodJournal journal = journalDao.read(null, null);
		assertNull(journal);

		// fetch valid user null date
		journal = journalDao.read(user, null);
		assertNull(journal);

		// fetch null user valid date
		journal = journalDao.read(null, validDate);
		assertNull(journal);

		// fetch valid user invalid date
		journal = journalDao.read(user, invalidDate);
		assertNull(journal);

		// fetch valid user valid date
		journal = journalDao.read(user, validDate);
		assertNotNull(journal);
	}

	@Test
	public void test_fetch_midnight() {
		User user = randomUser();
		DateTime midnight = new DateTime().withDate(2010, DateTimeConstants.MARCH, 1);

		FoodJournal journal = journalDao.read(user, midnight);
		assertNotNull(journal);
	}

	/**
	 * <p>
	 * <b>NOTICE:</b> This test requires pre-existing user journals for
	 * 2010.02.02 in the data source.
	 * </p>
	 */
	@Test
	public void test_delete() {
		// delete null journal
		try {
			journalDao.delete(null);
		}
		catch (Exception e) {
			fail(e.getMessage());
		}

		User user = randomUser();

		// fetch a valid journal
		FoodJournal journal = journalDao.read(user, new DateTime().withDate(2010, DateTimeConstants.MARCH, 2)
				);
		assertNotNull(journal);
		assertFalse(journal.isEmpty());

		// remove an entry to verify that everything gets deleted, active
		// entries and removed entries
		journal.removeEntry(journal.getEntries().get(0));
		journalDao.delete(journal);

		FoodJournal fetchedJournal = journalDao.read(user, new DateTime()
				.withDate(2010, DateTimeConstants.FEBRUARY, 2));
		assertNull(fetchedJournal);
	}

	private Food randomFood() {
		return foods.get(RandomUtils.nextInt(foods.size()));
	}

	private User randomUser() {
		return users.get(RandomUtils.nextInt(users.size() - 1));
	}
}
